package com.ziiwee.reduce.describe.utils;

import com.ziiwee.reduce.commons.utils.StringUtil;
import com.ziiwee.reduce.describe.HandlerDescribe;
import com.ziiwee.reduce.describe.ResourceMethod;
import com.ziiwee.reduce.describe.creators.handlers.*;
import com.ziiwee.reduce.rest.ResourceService;
import org.springframework.core.ResolvableType;
import org.springframework.data.domain.Page;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @author ziiwee
 * @data 2020/8/12
 */
public class DescribeUtil {

    public static String getSimpleNameByResolvableType(ResolvableType type) {
        Class<?> resolve = type.resolve();
        if (resolve == null) {
            return null;
        }
        String simpleName = resolve.getSimpleName();
        if (type.getGenerics().length > 0) {
            StringBuilder builder = new StringBuilder();
            for (ResolvableType generic : type.getGenerics()) {
                if (generic.resolve() != null) {
                    String name = getSimpleNameByResolvableType(generic);
                    builder.append(name).append(",");
                }
            }
            String names = builder.toString();
            if (StringUtil.hasLength(names)) {
                names = names.substring(0, names.length() - 1);
                simpleName = simpleName + "<" + names + ">";
            }
        }
        return simpleName;
    }

    public static List<ResolvableType> getTypeArgsByInstance(Object object, Class<?> targetInterface) {
        return getTypeArgsByResolvableType(ResolvableType.forInstance(object), targetInterface);
    }

    public static List<ResolvableType> getTypeArgsByClass(Class<?> cls, Class<?> targetInterface) {
        return getTypeArgsByResolvableType(ResolvableType.forClass(cls), targetInterface);
    }

    public static List<ResolvableType> getTypeArgsByResolvableType(ResolvableType type, Class<?> targetInterface) {
        List<ResolvableType> classes = new ArrayList<>();
        if (Objects.equals(type.resolve(), targetInterface)) {
            classes.addAll(Arrays.asList(type.getGenerics()));
        }
        Class<?> superType = type.getSuperType().resolve();
        if (superType != null && superType != Object.class) {
            classes.addAll(getTypeArgsByResolvableType(type.getSuperType(), targetInterface));
        }
        for (ResolvableType anInterface : type.getInterfaces()) {
            classes.addAll(getTypeArgsByResolvableType(anInterface, targetInterface));
        }
        return classes;
    }

    public static List<HandlerDescribe> createHandlers(String basePattern, ResourceService<?, ?> service, ResolvableType subjectType, ResolvableType idType) {
        return createHandlers(basePattern, service, subjectType, idType, false);
    }

    public static List<HandlerDescribe> createHandlers(String basePattern, ResourceService<?, ?> service, ResolvableType subjectType, ResolvableType idType, boolean isCommonService) {
        List<HandlerDescribe> handlers = new ArrayList<>();
        if (isCommonService) {
            handlers.add(new RestFindAllHandlerDescribeCreator(subjectType.resolve(), basePattern).create());
            handlers.add(new RestFindOneHandlerDescribeCreator(subjectType.resolve(), idType.resolve(), basePattern).create());
            handlers.add(new RestCreateHandlerDescribeCreator(subjectType.resolve(), basePattern).create());
            handlers.add(new RestUpdateHandlerDescribeCreator(subjectType.resolve(), idType.resolve(), basePattern).create());
            handlers.add(new RestDeleteHandlerDescribeCreator(subjectType.resolve(), idType.resolve(), basePattern).create());
            return handlers;
        }
        Method[] methods = service.getClass().getDeclaredMethods();
        for (Method method : methods) {
            Optional<ResourceMethod> first = Arrays.stream(ResourceMethod.values()).filter(it -> it.getMethodName().equals(method.getName())).findFirst();
            if (first.isPresent() && isOverrideMethod(method, subjectType, idType)) {
                if (first.get() == ResourceMethod.FIND_ALL) {
                    handlers.add(new RestFindAllHandlerDescribeCreator(subjectType.resolve(), basePattern).create());
                }
                if (first.get() == ResourceMethod.FIND_ONE) {
                    handlers.add(new RestFindOneHandlerDescribeCreator(subjectType.resolve(), idType.resolve(), basePattern).create());
                }
                if (first.get() == ResourceMethod.CREATE) {
                    handlers.add(new RestCreateHandlerDescribeCreator(subjectType.resolve(), basePattern).create());
                }
                if (first.get() == ResourceMethod.UPDATE) {
                    handlers.add(new RestUpdateHandlerDescribeCreator(subjectType.resolve(), idType.resolve(), basePattern).create());
                }
                if (first.get() == ResourceMethod.DELETE) {
                    handlers.add(new RestDeleteHandlerDescribeCreator(subjectType.resolve(), idType.resolve(), basePattern).create());
                }
            }
        }
        return handlers;
    }

    public static boolean isOverrideMethod(Method method, ResolvableType subjectType, ResolvableType idType) {
        if (method.getReturnType().equals(Object.class)) {
            return false;
        }
        if (method.getReturnType().equals(void.class)) {
            ResolvableType parameter = ResolvableType.forMethodParameter(method, 0);
            return Objects.equals(idType.resolve(), parameter.resolve());
        }
        if (method.getReturnType().equals(Page.class)) {
            ResolvableType generic = ResolvableType.forMethodReturnType(method).getGeneric(0);
            return Objects.equals(subjectType.resolve(), generic.resolve());
        }
        return Objects.equals(subjectType.resolve(), method.getReturnType());
    }

}
